package org.nerve.fs.mongo;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSDBFile;
import com.mongodb.gridfs.GridFSInputFile;
import org.nerve.fs.AbstractDFileSystem;
import org.nerve.fs.DFile;
import org.nerve.fs.DFileSystem;
import org.nerve.fs.common.FilenameUtils;
import org.nerve.fs.common.exceptions.FileAlreadyExistException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 封装GridFS的工具类
 * org.nerve.fs.mongo
 * Created by zengxm on 2015/9/28 0028.
 */
public class GFileSystem<C extends DefaultFSConfig> extends AbstractDFileSystem{

	private static final String ATTR_DIRECTORY = "directory";
	private static final String ATTR_MD5 = "md5";
	private static final String ATTR_PARENT = "parent";
	private static final String ATTR_NAME = "name";
	private static final String ATTR_FILE_NAME = "filename";

	protected static GridFS gridFS = null;

	private C config;

	private GFileSystem(){
	}
	private GFileSystem(C c){
		this.config = c;
	}

	/**
	 * 获取GFileSystem实例，
	 * @param db
	 * @return
	 */
	public static final GFileSystem getInstance(final DB db){
		DefaultFSConfig dConfig = new DefaultFSConfig();
		try{
			//加载配置文件
			Properties properties = new Properties();
			properties.load(GFileSystem.class.getResourceAsStream("/gridfs.properties"));

			dConfig.setNameSpace(properties.getProperty("nameSpace",dConfig.getNameSpace()));
			dConfig.setMaxFileSize(Long.valueOf(properties.getProperty(
							"maxFileSize",
							dConfig.getMaxFileSize()+"")
			));
			dConfig.setMd5Unique(Boolean.valueOf(properties.getProperty(
					"unique.md5"
			)));
		}catch(Exception e){
			System.err.println("failed to load gridfs.properties, use default values!");
		}

		if(gridFS == null){
			gridFS = new GridFS(db, dConfig.getNameSpace());
		}
		return new GFileSystem(dConfig);
	}

	public C getConfig() {
		return config;
	}
	public GFileSystem setConfig(C config) {
		this.config = config;
		return this;
	}

	/**
	 * 提交一个文件到DFS
	 * @param file
	 */
	@Override
	public void upload(File file) throws IOException {
		if(file == null || !file.exists() || file.isDirectory())
			return ;

		GridFSInputFile gFile = gridFS.createFile(file);

		String fullPath = FilenameUtils.toDFSPath(file.getAbsolutePath());
		gFile.setFilename(fullPath);
		gFile.setContentType(FilenameUtils.getExtension(file.getName()));
		gFile.put(ATTR_DIRECTORY, file.isDirectory());
		gFile.put(ATTR_PARENT, FilenameUtils.getFullPathNoEndSeparator(fullPath));
		gFile.put(ATTR_NAME, file.getName());

		//检查文件是否存在
		if(isExist(fullPath))
			throw new FileAlreadyExistException();

		//是否判断md5重复
		if(config.isMd5Unique()){
			GridFSDBFile dbFile = gridFS.findOne(new BasicDBObject(ATTR_MD5, md5HexOfFile(file)));
			if(dbFile != null){
				throw new FileAlreadyExistException();
			}
		}

		gFile.save();
	}

	@Override
	public void uploadDir(File directory, boolean withChildren) throws IOException {
		if(directory == null || directory.isFile())
			return ;
		//创建目录
		mkdirs(directory.getAbsolutePath());
		File[] files = directory.listFiles();
		for(File f:files){
			if(f.isFile())
				upload(f);
			else if(f.isDirectory() && withChildren){
				uploadDir(f, withChildren);
			}
		}
	}

	@Override
	public boolean isExist(String path) {
		return gridFS.findOne(new BasicDBObject(ATTR_FILE_NAME, path))!=null;
	}

	/**
	 * 得到指定目录的子文件总数（目录、文件）
	 * @param path
	 * @return
	 */
	protected long numberOfChildren(String path){
		return gridFS.getFileList(new BasicDBObject(ATTR_PARENT, FilenameUtils.toDFSPath(path))).count();
	}

	protected GridFSDBFile getDBFileByPath(String path){
		return gridFS.findOne(new BasicDBObject(ATTR_FILE_NAME, FilenameUtils.toDFSPath(path)));
	}

	@Override
	public DFile get(String path) {
		GridFSDBFile dbFile = getDBFileByPath(path);

		if(dbFile == null)
			return DFile.noExist();
		else {
			return convert(dbFile);
		}
	}

	protected DFile convert(GridFSDBFile dbFile){
		return new DFile(
				(Boolean) dbFile.get(ATTR_DIRECTORY),
				(String)dbFile.get(ATTR_PARENT),
				(String)dbFile.get(ATTR_NAME),
				dbFile.getLength(),
				dbFile.getInputStream()
		);
	}

	@Override
	public List<DFile> listFiles(String path) {
		List<GridFSDBFile> dbFiles = gridFS.find(new BasicDBObject(ATTR_PARENT, FilenameUtils.toDFSPath(path)), new BasicDBObject(ATTR_DIRECTORY,1));
		System.out.println(dbFiles.size()+"   on:"+FilenameUtils.toDFSPath(path));
		List<DFile> dFiles = new ArrayList<>();
		dbFiles.forEach(b->dFiles.add(convert(b)));
		return dFiles;
	}

	@Override
	public boolean delete(File file) {
		return delete(file.getAbsoluteFile());
	}

	@Override
	public boolean delete(String filePath) {
		//判断是否存在
		DFile dFile = get(FilenameUtils.toDFSPath(filePath));
		if(dFile == null || !dFile.exist())
			return false;

		//判断是否为空的目录
		if(dFile.isDirectory() && numberOfChildren(dFile.getAbsolutePath())>0)
			return false;

		//删除操作
		gridFS.remove(dFile.getAbsolutePath());
		return true;
	}

	@Override
	public boolean mkdir(String path) {
		return mkdir(DFile.build(path));
	}

	@Override
	public boolean mkdirs(String path) {
		//如果存在，就不需要重复创建
		DFile dFile = DFile.build(path);
		if(isExist(dFile.getAbsolutePath())){
			return false;
		}

		//尝试创建
		if(mkdir(dFile)){
			return true;
		}

		//到了这一步，则表示父目录不存在
		String parent = dFile.getParent();
		return (parent != null && !parent.equals(DFile.separator) && (mkdirs(parent) || isExist(parent)) &&
				mkdir(dFile));
	}

	/**
	 * 创建目录
	 * @param dFile         目录信息
	 */
	public boolean mkdir(DFile dFile){
		//先判断指定的目录是否存在
		if(isExist(dFile.getAbsolutePath()))
			return false;

		/*
		判断父目录是否存在， 如果父目录不存在，无法创建
		注意：
		如果DFile 位于一级目录（如 /log.txt, /abc)，则不判断
		 */
		if(!dFile.isRoot() && !isExist(dFile.getParent()))
			return false;

		GridFSInputFile gif = gridFS.createFile(new byte[]{});
		gif.setFilename(dFile.getAbsolutePath());
		gif.setContentType("directory");
		gif.put(ATTR_DIRECTORY, true);
		gif.put(ATTR_NAME, dFile.getName());
		gif.put(ATTR_PARENT, dFile.getParent());

		gif.save();
		return true;
	}

	@Override
	public void write(String path, String fileContent) {
		GridFSDBFile dbFile = getDBFileByPath(path);
		GridFSInputFile gif = gridFS.createFile(fileContent==null?new byte[]{}:fileContent.getBytes());
		if(dbFile != null)
			gif.setId(dbFile.getId());

		DFile dFile = DFile.build(path);
		write(gif, dFile, false);
	}

	@Override
	public void write(String path, InputStream is) {
		GridFSDBFile dbFile = getDBFileByPath(path);
		GridFSInputFile gif = gridFS.createFile(is);
		if(dbFile != null)
			gif.setId(dbFile.getId());
		DFile dFile = DFile.build(path);
		write(gif, dFile,false);
	}

	/**
	 *
	 * @param gif
	 * @param dFile
	 * @param removeOnExist
	 */
	protected void write(GridFSInputFile gif, DFile dFile, boolean removeOnExist){
		String fullPath = dFile.getAbsolutePath();
		//创建目录
		mkdirs(fullPath);

		gif.setFilename(fullPath);
		gif.setContentType(FilenameUtils.getExtension(fullPath));
		gif.put(ATTR_DIRECTORY, false);
		gif.put(ATTR_NAME, dFile.getName());
		gif.put(ATTR_PARENT, dFile.getParent());

		gif.save();
	}
}
